﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using SlamCms.Common;

namespace SlamCms.Data
{
	public static class QueryBuilderXmlExtensions
	{
		/// <summary>
		/// Appends a set of parameters defined in an XElement
		/// </summary>
		/// <param name="queryBuilder"></param>
		/// <param name="xQuery"></param>
		/// <returns></returns>
		public static QueryBuilder AppendXmlParameters(this QueryBuilder query, XElement xQuery)
		{
			foreach (var xParameter in xQuery.Descendants("parameter"))
			{
				AddParameter(query, xParameter);
			}

			return query;
		}

		private static void AddParameter(QueryBuilder query, XElement xParameter)
		{
			var name = xParameter.GetAttribute("name", true);

			var methods = typeof(QueryBuilder).GetMethods().Where(m => m.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)).ToList();

			if (!methods.Any())
				throw new Exception("There is no QueryBuilder method named {0}".F(name));

			foreach (var method in methods)
			{
				if (TryMethod(query, method, xParameter))
					return;
			}

			throw new Exception("The is no method {0} in QueryBuilder to invoke with the specified parameters".F(name));
		}

		private static bool TryMethod(QueryBuilder query, MethodInfo method, XElement xParameter)
		{
			try
			{
				var generic = xParameter.GetAttribute("generic");
				if (!generic.IsNullOrEmpty())
				{
					var types = generic.Split(',').Select(x => query.Context.Configuration.ContentTypes.GetTypeByName(x.Trim())).ToArray();
					method = method.MakeGenericMethod(types);
				}

				var parameters = new List<object>();
				var methodParameters = method.GetParameters();
				foreach (var parameter in methodParameters)
				{
					var value = xParameter.GetAttribute(parameter.Name, true);
					var parsedValue = Convert.ChangeType(value, parameter.ParameterType);
					parameters.Add(parsedValue);
				}

				method.Invoke(query, parameters.ToArray());

				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}
	}
}